module jinji.tag.service;

import jinji.common.type;
import jinji.config;
import jinji.rpc;
import jinji.tag.model;
import jinji.util.query;
import jinji.util.string;

public import jinji.util.tag;

/// 标签是否存在
bool exists(string tag)
	=> has!(Tag, "name=", tag);

extern (C):

/// 解析别名
auto resolve(string name) {
	auto q = query!(SB.select!(TagAlias.to)
			.where("name=$1"))(name);
	return q.get(name);
}

/// 验证别名
Tags parseAliases(string err = "别名不能与原名相同")(string name, string to) {
	import tame.text.ascii : cmp;

	Cstr lastTag;
	foreach (tag; Splitter(to)) {
		tag = resolve(parseTag(tag));
		const c = cmp(tag, lastTag);
		checkArg(to.stringof, c > 0, text("标签'", tag, "'重复或未按顺序排列"));
		checkArg(to.stringof, tag != name, err);
		lastTag = tag;
	}
	return cast(Tags)to;
}

/// 验证隐含标签
alias parseImplies = parseAliases!"隐含标签不能与原名相同";

/// 获取标签交集
extern (D) long overlapCount(in char[] a, in char[] b) {
	if (a == b)
		return tagCount(a);
	return query!(SB.select!"count(*)"
			.from("tagidx")
			.where(`tagidx MATCH '"'||$1||'" "'||$2||'"'`))(a, b).get!long;
}

/// 获取标签使用数
extern (D) long tagCount(in char[] tag) {
	auto q = query!(SB.select!(Tag.count)
			.where("name=$1"))(tag);
	auto d = query!(SB.select!(TagCountDelta.delta)
			.where("name=$1"))(tag);
	return q.get(0L) + d.get(0L);
}

/++
导入标签
Params:
	tags = 标签串
	category = 分类
	group = 分组
	uid = 用户ID
Return:
	导入的标签数
+/
long importTags(string tags, Name category, Tags group, Id uid) {
	import tame.string : splitter;

	db.begin();
	scope (failure)
		db.rollback();
	long count;
	foreach (line; tags.splitter('\n')) {
		Name cat = category;
		Tags g = group;
		auto s = line.splitter('\t');
		const tag = parseTag(s.front);
		s.popFront();
		if (!s.empty) {
			cat = parseName(s.front);
			s.popFront();
			g = parseTags(s.s);
		}
		count += exec!(SB.insert!(Tag, skipOnCreate) ~ " ON CONFLICT(name) DO " ~
				SB.update("")
					.set(`category=$2,"group"=$3,updated_at=now()`)
					.where("name=$1"))(tag, cat, g, 0, uid);
	}
	db.commit();
	return count;
}

/++
导出标签
Params:
	r = 输出流
+/
void exportTags(R)(ref scope R r) {
	auto q = query!(SB.select!(Tag.name, Tag.category, Tag.group)
			.orderBy("name"));
	foreach (t; q) {
		r.put(t.get!string);
		const category = t.get!(string, 1);
		const group = t.get!(string, 2);
		if ((category.length && category != defCategory) || group.length) {
			r.put('\t');
			r.put(category);
		}
		if (group.length) {
			r.put('\t');
			r.put(group);
		}
		r.put('\n');
	}
}

/++
获取相关标签
Params:
	tags = 标签串
	limit = 限制数
Return:
	相关标签
+/
auto relatedTags(string tags, int limit) {
	auto r = fixedBuf!(1024, "标签串过长");
	r ~= `with a as (SELECT name,"count" from tag order by "count" desc limit 2000),` ~
		`b as (SELECT name FROM tag where name in(`;
	escapeTags(tags, r);
	r ~= `) order by "count" desc limit 20)` ~
		`SELECT DISTINCT a.name from a cross join b` ~
		` order by cossim(a.name,b.name) desc LIMIT $1`;
	auto q = query(r.data, limit);
	return q;
}

/++
转义标签串
Params:
	tags = 标签串
	sink = 输出流
+/
void escapeTags(S)(string tags, ref scope S sink) @trusted if (isStringSink!S) {
	for (auto s = Splitter(tags); !s.empty;) {
		sink.put('\'');
		auto tag = s.front;
		size_t start;
		foreach (i, c; tag) {
			checkArgRange(tags.stringof, i < maxTagLength, "标签名过长");
			string escaped = void;

			// dfmt off
			switch (c) {
			case '\'': escaped = `''`; break;
			default:
				checkArg(tags.stringof, c >= ' ', "标签名包含非法字符");
				continue;
			}
			// dfmt on

			sink.put(tag[start .. i]);
			sink.put(escaped);
			start = i + 1;
		}

		sink.put(tag[start .. $]);
		s.popFront();
		if (s.empty)
			sink.put('\'');
		else
			sink.put(`',`);
	}
}

/++
转义标签
Params:
	tags = 标签
	sink = 输出流
+/
extern (D) void escapeTag(S)(in char[] tag, ref S sink) @trusted if (isStringSink!S) {
	size_t start;

	foreach (i, c; tag) {
		string escaped = void;

		switch (c) {
		case '\\':
			throw new ArgEx(_(`无效的字符：'\'`));
		case '_':
			escaped = `\_`;
			break;
		case '%':
			escaped = `\%`;
			break;
		default:
			continue;
		}

		sink.put(tag[start .. i]);
		sink.put(escaped);
		start = i + 1;
	}

	sink.put(tag[start .. $]);
}

/// 应用标签别名
Tags applyTagAliases(Tags tags) {
	import std.array : appender;

	auto r = appender!string;
	foreach (tag; Splitter(tags)) {
		auto t = resolve(tag);
		if (r[].length) {
			r ~= ' ';
		}
		r ~= t;
	}
	return cast(Tags)r[];
}

/// 应用隐含标签
Tags applyImplicitTags(Tags tags) @trusted {
	import jinji.util.tag;

	string r = void;
	do {
		r = tags;
		foreach (tag; Splitter(r)) {
			auto q = query!(SB.select!(TagImplications.tags)
					.where("name=$1"))(tag);
			if (!q.empty) {
				r = mergePatch(r, q.get!string);
			}
		}
	} while (r != tags);
	return cast(Tags)r;
}

/// 更新标签的使用数
void updateCount() {
	db.begin();
	scope (failure)
		db.rollback();
	foreach (q; query!(SB.select!TagCountDelta
			.where("delta!=0"))) {
		const tcd = q.get!TagCountDelta;
		exec!(SB.update!Tag.set("count=count+$1").where("name=$2"))(tcd.delta, tcd.name);
	}
	exec!(SB.del!TagCountDelta);
	db.commit();
}
